--[[=========================================================
	CLIENT FILES
=========================================================]]--
if SERVER then
	AddCSLuaFile( "shared.lua" )
end

--[[=========================================================
	CONFIGURATION
=========================================================]]--

--[[-----------------------------------------------------]]--
--	Client only
--[[-----------------------------------------------------]]--
if CLIENT then
	SWEP.DrawCrosshair								= false
end

--[[-----------------------------------------------------]]--
--	Shared
--[[-----------------------------------------------------]]--
SWEP.Base											= "weapon_base"
SWEP.Category           							= "Sigma.Core"
SWEP.Firemodes										= {}

--[[-----------------------------------------------------]]--
--	DT accessors
--[[-----------------------------------------------------]]--
AccessorFuncDT( SWEP, "shooting", "Shooting" )
AccessorFuncDT( SWEP, "ironsights", "Ironsights" )
AccessorFuncDT( SWEP, "firemode", "Firemode" )

--[[=========================================================
	FUNCTIONS
=========================================================]]--

--[[-----------------------------------------------------]]--
--	Lookup for a weapon firemode info
--[[-----------------------------------------------------]]--
function SWEP:FindFiremode( mode )

	--	Variables
	local index										= 0
	
	--	Parameter is an index
	if IsInt( mode ) then
		index										= mode
		
		--	If not valid, set it to 0
		if index < 0 then
			index									= 0
		end
		
	--	Parameter is a name
	elseif IsString( mode ) then
		
		--	Search firemode
		for i,v in ipairs( self.Firemodes ) do
			if v.Firemode.Name == mode then
				index								= i
				break
			end
		end
		
	end
	
	--	Return false if not found
	if index == 0 then return false end
	
	--	Otherwise return the firemode
	return self.Firemodes[index]

end

--[[-----------------------------------------------------]]--
--	Returns the current weapon firemode info
--[[-----------------------------------------------------]]--
function SWEP:WeaponFiremode()
	return self:FindFiremode( self:GetFiremode() )
end

--[[-----------------------------------------------------]]--
--	Returns the current firemode info
--[[-----------------------------------------------------]]--
function SWEP:Firemode()
	local wpf										= self:WeaponFiremode()
	if wpf and wpf.Firemode then
		return wpf.Firemode
	else
		return nil
	end
end

--[[-----------------------------------------------------]]--
--	SWEP Configuration from data object classes
--	Pass an ObjWeapon instance and some ObjFiremode instances
--[[-----------------------------------------------------]]--
function SWEP:Setup( objWeapon, ... )

	--	Configure shared weapon members
	self.AdminSpawnable								= tobool( objWeapon.AdminSpawnable )
	self.AutoSwitchFrom								= tobool( objWeapon.AutoSwitchFrom )
	self.AutoSwitchTo								= tobool( objWeapon.AutoSwitchTo )
	self.Spawnable									= tobool( objWeapon.Spawnable )
	
	--	Configure client weapon members
	if CLIENT then
		self.CSMuzzleFlashes						= tobool( objWeapon.CSMuzzleFlashes )
	end
	
	--	Prepare firemodes, server-side
	if SERVER then
		for i,v in ipairs( {...} ) do
		
			--	Store firemode reference
			if IsValidInstance( v, ObjWeaponFiremode ) then
				table.insert( self.Firemodes, v )
				
			--	Invalid object
			else
				DEBUG:ErrMsg( "Invalid firemode given at index " .. tostring( i ), "SWEP.Setup" )
			end
		
		end
		
		--	Prepare firemodes, client-side
	else
		for i,v in ipairs( ... ) do
		
			--	Store firemode reference
			if IsTable( v ) then
				table.insert( self.Firemodes, v )
				DEBUG:Msg( "Registered firemode " .. v.Firemode.Display )
				
			--	Invalid object
			else
				DEBUG:ErrMsg( "Invalid firemode given at index " .. tostring( i ), "SWEP.Setup" )
			end
		
		end
	end
	
	--	Set first firemode by default
	if table.Count( self.Firemodes ) > 0 then
		self:SetFiremode( 1 )
	else
		return
	end
	
	--	Get firemode
	local mode										= self:FindFiremode( self:GetFiremode() )
	
	--	Fill clip
	if CLIENT and self.Weapon:Clip1() == -1 then
		self.Weapon:SetClip1( mode.Firemode.DefaultClip )
	end
	
	--	Deploy speed
	self:SetDeploySpeed( mode.Firemode.DeployDelay )
	
	--	Hold type
	if self.SetWeaponHoldType then
		self:SetWeaponHoldType( mode.HoldType or "pistol" )
	end
	
	--	Precache resources ( sounds & models )
	self:Precache()
	
	--	Configure the client
	if SERVER and not SinglePlayer then
		net.Start( "MsgSetup" )
		for i,v in ipairs( {...} ) do
			net.WriteTable( v:GetClientTable() )
		end
		net.Send( self.Owner )
	end
end

--[[-----------------------------------------------------]]--
--	Do shot actions
--[[-----------------------------------------------------]]--
function SWEP:Shoot( firemode )

	--	Weapon sound
	self.Weapon:EmitSound( firemode.ShootSound.FileName )
	
	--	Shoot a bullet
	self:ShootBullet( 50, 0.2 )
	
	--	Take ammo
	self:TakePrimaryAmmo( 1 )
	
	--	Punch view
	if not IsValid( self.Owner ) or self.Owner:IsNPC() or ( not self.Owner.ViewPunch ) then return end
	
	self.Owner:ViewPunch( Angle(
		math.Rand( -firemode.RecoilDown, firemode.RecoilUp ) * 0.2,
		math.Rand( -firemode.RecoilLeft, firemode.RecoilRight ) * 0.2,
		0
	) )

end

--[[-----------------------------------------------------]]--
--	Do empty shot actions
--[[-----------------------------------------------------]]--
function SWEP:DryFire( firemode )

	--	Empty sound
	if CLIENT and LocalPlayer() == self.Owner then
      self:EmitSound( firemode.EmptySound.FileName )
   end

end

--[[=========================================================
	NET MESSAGES
=========================================================]]--

--[[-----------------------------------------------------]]--
--	Rcv: Setup client SWEP
--[[-----------------------------------------------------]]--
SWEP.RcvMsgSetup									= function( self, len )

	--	Debug
	DEBUG:Msg( "RECEIVED SETUP MESSAGE" )
	
	--	Only for client
	if SERVER then
		DEBUG:ErrMsg( "This shouldn't be called server-side", "SWEP.RcvMsgSetup" )
		return
	end
	
	--	Retrieve the firemodes
	local objWepFiremodes							= net.ReadTables()
	
	--	Setup
	if IsTable( objWepFiremodes ) and table.Count( objWepFiremodes ) > 1 then
		self:Setup( objWepFiremodes[1]["Weapon"], objWepFiremodes )
	else
		DEBUG:ErrMsg( "Empty configuration received", "SWEP.RcvMsgSetup" )
	end

end

--[[=========================================================
	EVENTS
=========================================================]]--

--[[-----------------------------------------------------]]--
--	Precache resources
--[[-----------------------------------------------------]]--
function SWEP:Precache()

	--	Net strings
	if SERVER and not self.IsNetPrecached then
	
		--	Debug
		DEBUG:Msg( "PRECACHING NETWORK STRINGS" )
	
		--	Preache net strings
		util.AddNetworkString( "MsgSetup" )
		
		--	Flag us as ready
		self.IsNetPrecached							= true
	end
	
	--	Precache firemodes resources
	if IsTable( self.Firemodes ) and table.Count( self.Firemodes ) > 0 then
		DEBUG:Msg( "Precaching firemodes" )
		for k, wpf in pairs( self.Firemodes ) do
			if IsTable( wpf.Firemode ) and table.Count( wpf.Firemode ) > 0 then
				DEBUG:Msg( "Precaching for " .. tostring( wpf.Firemode.Display ) )
				for i, snd in ipairs( wpf.Firemode ) do
				
					--	Sounds
					if IsValideInstance( snd, ObjSoundInfo ) then
						DEBUG:Msg( "Precaching sound " .. tostring( snd.FileName ) )
						util.PrecacheSound( snd.FileName )
					end
				end
			end
		end
	end
end

--[[-----------------------------------------------------]]--
--	Initialize
--[[-----------------------------------------------------]]--
function SWEP:Initialize()

	--	Precache resources
	self:Precache()

	--	Initialize networked vars
	if SERVER then
		self:SetIronsights( false )
		self:SetShooting( false )
	end
	
	--	Deploy speed
	self:SetDeploySpeed( 1 )
	
	--	Set elapsed shooting time
	self.ShootingTime = CurTime()
	
	--	Hold type
	if self.SetWeaponHoldType then
		self:SetWeaponHoldType( "smg" )
	end
	
	--	Prepare net messages
	if CLIENT then
		net.Receive( "MsgSetup", function( len )
			self:RcvMsgSetup( len )
		end)
	end

end

--[[-----------------------------------------------------]]--
--	Create networking variables
--[[-----------------------------------------------------]]--
function SWEP:SetupDataTables()
	self:DTVar( "Bool", 1, "shooting" )
	self:DTVar( "Bool", 2, "ironsights" )
	self:DTVar( "Int", 1, "firemode" )
end

--[[-----------------------------------------------------]]--
--	Primary attack
--[[-----------------------------------------------------]]--
function SWEP:PrimaryAttack()

	--	No firemode, nothing
	if not self:Firemode() then return end
	
	--	Get current firemode
	local firemode									= self:Firemode()

	--	Next use time
	self.Weapon:SetNextPrimaryFire( CurTime() + firemode.ShootDelay )
	
	--	Not allowed yet
	if not self:CanPrimaryAttack() then return end
	
	--	Busy shooting
	self:SetShooting( true )
	self.ShootingTime = CurTime()
	
	--	Without burst
	if firemode.BurstCount < 2 then
	
		--	Shoot if we still have ammo
		if self.Weapon:Clip1() > 0 then
			self:Shoot( firemode )
			
		--	Otherwise do a dry shot
		else
			self:DryFire( firemode )
		end
		
		--	No more busy shooting
		self:SetShooting( false )
		
	--	With burst
	else
		for i = 1, firemode.BurstCount do
			timer.Simple(
				firemode.BurstDelay * ( i - 1 ),
				function()
					
					--	Shoot if we still have some ammo left
					if self.Weapon:Clip1() > 0 then
						self:Shoot( firemode )
					
					--	Otherwise do a dry shot
					else
						self:DryFire( firemode )
					end
					
					--	No more busy shooting
					if i == firemode.BurstCount then
						self:SetShooting( false )
					end
				end
			)
		end
	end
end

--[[-----------------------------------------------------]]--
--	Use iron sights
--[[-----------------------------------------------------]]--
function SWEP:SecondaryAttack()
	self:SetIronsights( true )
end

--[[-----------------------------------------------------]]--
--	Tells whether primary attack is allowed or not
--[[-----------------------------------------------------]]--
function SWEP:CanPrimaryAttack()

	--	Owner player must be valid
	if not IsValid( self.Owner ) then return end

	--	Primary clip must not be empty
	--if self.Weapon:Clip1() <= 0 then return false end
	
	--	Player should not be shooting ( used mainly for bursts )
	if self:Firemode() and self:GetShooting() then return false end
   
   return not self:CheckHitsWall().Hit
end

--[[-----------------------------------------------------]]--
--	Tells whether using sights is allowed or not
--[[-----------------------------------------------------]]--
function SWEP:CanSecondaryAttack()

	--	Owner player must be valid
	if not IsValid( self.Owner ) then return end
   
   return not self:CheckHitsWall().Hit
end

--[[-----------------------------------------------------]]--
--	Gets primary ammo left
--[[-----------------------------------------------------]]--
function SWEP:Ammo1()
   return	IsValid( self.Owner ) and
			self:GetFiremode() and
			self:Firemode() and
			self.Owner:GetAmmoCount( self:Firemode().AmmoType ) or
			false
end

--[[-----------------------------------------------------]]--
--	Weapon is deployed
--[[-----------------------------------------------------]]--
function SWEP:Deploy()
   self:SetIronsights( false )
   self:SetShooting( false )
   return true
end

--[[-----------------------------------------------------]]--
--	Weapon gets reloaded
--[[-----------------------------------------------------]]--
function SWEP:Reload()
   self:SetIronsights( false )
   self:SetShooting( false )
   
   --	Do nothing if we don't have a firemode in use
   if not self:Firemode() then return end
   
   --	Reload anim
   self.Weapon:DefaultReload( self:Firemode().ReloadActivity.Activity )
end

--[[-----------------------------------------------------]]--
--	Weapon is restored
--[[-----------------------------------------------------]]--
function SWEP:OnRestore()
   self:SetIronsights( false )
   self:SetShooting( false )
end

--[[-----------------------------------------------------]]--
--	Weapon is equiped
--[[-----------------------------------------------------]]--
function SWEP:Equip( ply )
	if SERVER and self:IsOnFire() then
		self:Extinguish()
	end
end

--[[-----------------------------------------------------]]--
--	Draw weapon selection
--[[-----------------------------------------------------]]--
function SWEP:DrawWeaponSelection() end

--[[-----------------------------------------------------]]--
--	Every think
--[[-----------------------------------------------------]]--
function SWEP:Think()

	--	Reset iron sights
	if self.Owner:KeyReleased( IN_ATTACK2 ) and self:GetIronsights() then
		self:SetIronsights( false )
	end
	
	--	Reset shooting
	if SERVER and self:GetShooting() and CurTime() - self.ShootingTime > 0.5 then
		self:SetShooting( false )
	end
	
	--	Custom / normal crosshair
	if CLIENT then
		self.DrawCrosshair = not tobool( GetConVarNumber( "sc_custom_crosshair" ) )
	end
end

--[[-----------------------------------------------------]]--
--	Shoots a bullet
--[[-----------------------------------------------------]]--
function SWEP:ShootBullet( dmg, recoil, numbul, cone )

	--	No firemode, nothing
	if not self:GetFiremode() then return end
	
	--	Get current firemode
	local mode										= self:WeaponFiremode()

	--	Plays the animation
	self.Weapon:SendWeaponAnim( mode.Firemode.ShootActivity.Activity )

	--	Muzzle flash & player animation
	self.Owner:MuzzleFlash()
	self.Owner:SetAnimation( PLAYER_ATTACK1 )

   if not IsFirstTimePredicted() then return end

   local sights = self:GetIronsights()

   numbul = numbul or 1
   cone   = cone   or 0.01

   local bullet = {}
   bullet.Num    = numbul
   bullet.Src    = self.Owner:GetShootPos()
   bullet.Dir    = self.Owner:GetAimVector()
   bullet.Spread = Vector( cone, cone, 0 )
   bullet.Force  = 10
   bullet.Damage = dmg

   --	Fire bullets
   self.Owner:FireBullets( bullet )

   --	Skip next if owner is not a valid and alive player
   if ( not IsValid( self.Owner ) ) or ( not self.Owner:Alive() ) or self.Owner:IsNPC() then return end

	if ( ( game.SinglePlayer() and SERVER ) or
       ( ( not game.SinglePlayer() ) and CLIENT and IsFirstTimePredicted() ) ) then

		recoil = sights and ( recoil * 0.6 ) or recoil

		local eyeang = self.Owner:EyeAngles()
		eyeang.pitch = eyeang.pitch - recoil
		self.Owner:SetEyeAngles( eyeang )
   end

end

/* Check if it hits a wall */
function SWEP:CheckHitsWall()

	--	Firemode
	local firemode		= self:Firemode()
	if !IsTable( firemode ) then return false end

	/* Variables */
	local ply			= self.Owner
	local wpn			= self.Weapon
	local start			= self.Owner:GetShootPos()
	local aim			= self.Owner:GetAimVector()
	local len			= firemode.WallTransform.CheckDistance
	
	/* Create a trace from the weapon */
	local traceData		= {}
	traceData.start		= start
	traceData.endpos	= start + aim * len
	traceData.filter	= { [1] = ply, [2] = wpn }
	traceData.mask		= MASK_SOLID_BRUSHONLY
	local trace			= util.TraceLine( traceData )
	
	/* Return the trace info */
	return trace
end

--[[-----------------------------------------------------]]--
--	View
--[[-----------------------------------------------------]]--
if CLIENT then

function SWEP:GetViewModelPosition( pos, ang )
	
	/* Init last frame time if needed */
	if not self.LastFrameTime then self.LastFrameTime = CurTime() end
	
	/* Time elapsed since last frame */
	local timeElapsed = CurTime() - self.LastFrameTime
	
	/* Calculate pos and ang for Iron Sights */
	pos, ang = self:CalculateIronSights( timeElapsed, pos, ang )
	
	/* Calculate pos and ang for Running */
	pos, ang = self:CalculateRunning( timeElapsed, pos, ang )
	
	/* Calculate pos and ang for close walls */
	pos, ang = self:CalculateWalls( pos, ang )
	
	/* Update last frame time */
	self.LastFrameTime = CurTime()
	
	/* Return new values */
	return pos, ang

end

function SWEP:CalculateIronSights( timeElapsed, pos, ang )

	--	Firemode
	local firemode		= self:Firemode()
	if !IsTable( firemode ) then return pos, ang end

	/* Locals */
	local sightsOn		= self:GetIronsights()
	local shooting		= self:GetShooting()
	local sightsTime	= 0
	local sightsPos		= firemode.SightsTransform.Position
	local sightsAng		= firemode.SightsTransform.Angles
	
	/* Get the total time to switch */
	if sightsOn then
		sightsTime		= firemode.SightsInDelay
	else
		sightsTime		= firemode.SightsOutDelay
	end
	
	/* Iron sights are active */
	if sightsOn then
	
		/* If the elapsed time hasn't been created yet, do it */
		self.IronSightsElapsed = self.IronSightsElapsed or 0
		
		/* Update elapsed */
		self.IronSightsElapsed = math.Clamp( self.IronSightsElapsed + timeElapsed, 0, sightsTime )
		
	/* Iron sights are inactive */
	else
	
		/* If the elapsed time hasn't been created yet, do it */
		self.IronSightsElapsed = self.IronSightsElapsed or sightsTime
		
		/* Update elapsed */
		self.IronSightsElapsed = math.Clamp( self.IronSightsElapsed - timeElapsed, 0, sightsTime )
		
	end
	
	/* Calculate multiplier (ratio: 0 <= mul <= 1) */
	local mul = self.IronSightsElapsed / sightsTime
	
	/* Calculate angles */
	ang:RotateAroundAxis( ang:Right(), sightsAng.p * mul )
	ang:RotateAroundAxis( ang:Up(), sightsAng.y * mul )
	ang:RotateAroundAxis( ang:Forward(), sightsAng.r * mul )
	
	/* Calculate position */
	pos = pos + sightsPos.x * ang:Right() * mul
	pos = pos + sightsPos.y * ang:Forward() * mul
	pos = pos + sightsPos.z * ang:Up() * mul
	
	/* Return new values */
	return pos, ang
end

/* Calculate pos and ang for Running */
function SWEP:CalculateRunning( timeElapsed, pos, ang )

	--	Firemode
	local firemode		= self:Firemode()
	if !IsTable( firemode ) then return pos, ang end

	/* Locals */
	local shooting		= self:GetShooting()
	local ironsights	= self:GetIronsights()
	local runningTime	= 0
	local runningPos	= firemode.RunTransform.Position
	local runningAng	= firemode.RunTransform.Angles
	local vel			= self.Owner:GetVelocity():Length()
	
	/* Do nothing if we're not shooting or are using sights */
	if shooting or ironsights then return pos, ang end
	
	/* Calculate multiplier (ratio: 0 <= mul <= 1) */
	local mul = vel / 250
	
	/* Add smoothness */
	self.LastHitRunMul		= self.LastHitRunMul or mul
	mul						= self.LastHitRunMul - ( self.LastHitRunMul - mul ) * 0.25
	mul						= math.Round( mul, 4 )
	self.LastHitRunMul		= mul
	
	/* Calculate angles */
	ang:RotateAroundAxis( ang:Right(), runningAng.p * mul )
	ang:RotateAroundAxis( ang:Up(), runningAng.y * mul )
	ang:RotateAroundAxis( ang:Forward(), runningAng.r * mul )
	
	/* Calculate position */
	pos = pos + runningPos.x * ang:Right() * mul
	pos = pos + runningPos.y * ang:Forward() * mul
	pos = pos + runningPos.z * ang:Up() * mul
	
	/* Return new values */
	return pos, ang
end

/* Calculate pos and ang for close walls */
function SWEP:CalculateWalls( pos, ang )

	--	Firemode
	local firemode		= self:Firemode()
	if !IsTable( firemode ) then return pos, ang end

	/* Check if the SWEP hits a wall */
	local trace			= self:CheckHitsWall()
	local mul			= 0
	local offsetPos		= firemode.WallTransform.Position
	local offsetAng		= firemode.WallTransform.Angles
	local len			= firemode.WallTransform.CheckDistance
	
	/* Calculate multiplier (ratio: 0 <= mul <= 1) */
	if trace.Hit then
		local trLen			= trace.HitPos - self.Owner:GetShootPos()
		local trSqX			= math.pow( trLen.x, 2 )
		local trSqY			= math.pow( trLen.y, 2 )
		local trSqZ			= math.pow( trLen.z, 2 )
		local length		= math.sqrt( trSqX + trSqY + trSqZ )
		mul					= 1 - ( length / len )
	end
	
	/* Add smoothness */
	self.LastWallHitMul		= self.LastWallHitMul or mul
	mul						= self.LastWallHitMul - ( self.LastWallHitMul - mul ) * 0.135
	mul						= math.Round( mul, 4 )
	self.LastWallHitMul		= mul
	
	/* Calculate angles */
	ang:RotateAroundAxis( ang:Right(), offsetAng.p * mul )
	ang:RotateAroundAxis( ang:Up(), offsetAng.y * mul )
	ang:RotateAroundAxis( ang:Forward(), offsetAng.r * mul )
	
	/* Calculate position */
	pos = pos + offsetPos.x * ang:Right() * mul
	pos = pos + offsetPos.y * ang:Forward() * mul
	pos = pos + offsetPos.z * ang:Up() * mul
	
	/* Return new values */
	return pos, ang
end

end